﻿// ***********************************************************************
// Assembly         : Tx.OCC
// Author           : tianteng
// Created          : 06-24-2019
//
// Last Modified By : tianteng
// Last Modified On : 06-24-2019
// ***********************************************************************
// <copyright file="OccShapeEx.cs" company="北京腾雪科技有限责任公司">
//     Copyright © 北京腾雪科技有限责任公司 2022
// </copyright>
// <summary></summary>
// ***********************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using Occt;

namespace Tx.OCC
{
    /// <summary>
    ///     Class OccShapeEx.
    /// </summary>
    public static class OccTopoDSShapeEx
    {
        /// <summary>
        ///     Gets the shape enum.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type">The type.</param>
        /// <returns>TopAbs_ShapeEnum.</returns>
        public static TopAbs_ShapeEnum ShapeEnum(this Type type)
        {
            if (typeof(TopoDS_Compound).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_COMPOUND;
            if (typeof(TopoDS_CompSolid).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_COMPSOLID;
            if (typeof(TopoDS_Solid).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_SOLID;
            if (typeof(TopoDS_Shell).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_SHELL;
            if (typeof(TopoDS_Face).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_FACE;
            if (typeof(TopoDS_Wire).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_WIRE;
            if (typeof(TopoDS_Edge).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_EDGE;
            if (typeof(TopoDS_Vertex).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_VERTEX;
            if (typeof(TopoDS_Shape).IsAssignableFrom(type))
                return TopAbs_ShapeEnum.TopAbs_SHAPE;
            throw new Exception($"{nameof(type)}不是TopoDS_Shape类型！");
        }

        /// <summary>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static TopAbs_ShapeEnum ShapeEnum<T>() where T : TopoDS_Shape
        {
            return ShapeEnum(typeof(T));
        }

        /// <summary>
        /// 子实体
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="shape"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static IEnumerable<T> SubShapes<T>(this TopoDS_Shape shape)
            where T : TopoDS_Shape
        {
            var shapeEnum = ShapeEnum<T>();

            if (shape == null)
                throw new ArgumentNullException(nameof(shape));
            for (var it = new TopoDS_Iterator(shape); it.More; it.Next())
            {
                if (it.Value.ShapeType == shapeEnum || typeof(T) == typeof(TopoDS_Shape))
                {
                    var instance = it.Value as T;
                    yield return instance;
                }
            }
        }

        /// <summary>
        ///     Gets the enumerable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="shape">The shape.</param>
        /// <returns>IEnumerable&lt;T&gt;.</returns>
        public static IEnumerable<T> Explorer<T>(this TopoDS_Shape shape)
            where T : TopoDS_Shape
        {
            var shapeEnum = ShapeEnum<T>();

            if (shape == null)
                throw new ArgumentNullException(nameof(shape));
            for (var explorer = new TopExp_Explorer(shape, shapeEnum, TopAbs_ShapeEnum.TopAbs_SHAPE);
                 explorer.More;
                 explorer.Next())
            {
                var instance = explorer.Current as T;
                yield return instance;
            }
        }

        /// <summary>
        ///     Distincts the specified enumerable.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable">The enumerable.</param>
        /// <returns>IEnumerable&lt;T&gt;.</returns>
        public static IEnumerable<T> Distinct<T>(this IEnumerable<T> enumerable) where T : TopoDS_Shape
        {
            return enumerable.Distinct((t1, t2) => t1.IsSame(t2));
        }

        /// <summary>
        ///     Comps the solids.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_CompSolid&gt;.</returns>
        public static IEnumerable<TopoDS_CompSolid> CompSolids(this TopoDS_Shape shape, bool distinct = true)
        {
            var compSolids = shape.Explorer<TopoDS_CompSolid>();
            if (distinct)
                compSolids = compSolids.Distinct();
            return compSolids;
        }

        /// <summary>
        ///     Solidses the specified distinct.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_Solid&gt;.</returns>
        public static IEnumerable<TopoDS_Solid> Solids(this TopoDS_Shape shape, bool distinct = true)
        {
            var solids = shape.Explorer<TopoDS_Solid>();
            if (distinct)
                solids = solids.Distinct();
            return solids;
        }


        /// <summary>
        ///     Shellses the specified distinct.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_Shell&gt;.</returns>
        public static IEnumerable<TopoDS_Shell> Shells(this TopoDS_Shape shape, bool distinct = true)
        {
            var shells = shape.Explorer<TopoDS_Shell>();
            if (distinct)
                shells = shells.Distinct();
            return shells;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        public static IEnumerable<T> ToEnumerable<T>(this TopExp_Explorer exp) where T : TopoDS_Shape
        {
            while (exp.More)
            {
                yield return exp.Current as T;
                exp.Next();
            }
        }

        /// <summary>
        ///     Faceses the specified distinct.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_Face&gt;.</returns>
        public static List<TopoDS_Face> Faces(this TopoDS_Shape shape, bool distinct = true)
        {
            var faces = new List<TopoDS_Face>();
            var exp = new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_FACE, TopAbs_ShapeEnum.TopAbs_SHAPE);
            foreach (var face in exp.ToEnumerable<TopoDS_Face>())
            {
                if (distinct)
                {
                    var otherIndex = faces.FindIndex(e => e.IsSame(face));
                    if (otherIndex >= 0)
                    {
                        if (faces[otherIndex].Orientation == TopAbs_Orientation.TopAbs_REVERSED
                            && face.Orientation == TopAbs_Orientation.TopAbs_FORWARD)
                            // Replace with forward face, this is prefered
                            faces[otherIndex] = face;

                        // Edge already present or replaced, skip adding
                        continue;
                    }
                }

                faces.Add(face);
            }

            return faces;
        }


        /// <summary>
        ///     Wireses the specified distinct.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_Wire&gt;.</returns>
        public static List<TopoDS_Wire> Wires(this TopoDS_Shape shape, bool distinct = true)
        {
            var wires = new List<TopoDS_Wire>();

            var exp = new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_WIRE, TopAbs_ShapeEnum.TopAbs_SHAPE);
            while (exp.More)
            {
                var wire = exp.Current as TopoDS_Wire;
                exp.Next();

                if (distinct)
                {
                    var otherEdgeIndex = wires.FindIndex(e => e.IsSame(wire));
                    if (otherEdgeIndex >= 0)
                    {
                        if (wires[otherEdgeIndex].Orientation == TopAbs_Orientation.TopAbs_REVERSED
                            && wire.Orientation == TopAbs_Orientation.TopAbs_FORWARD)
                            // Replace with forward wire, this is prefered
                            wires[otherEdgeIndex] = wire;

                        // Edge already present or replaced, skip adding
                        continue;
                    }
                }

                wires.Add(wire);
            }

            return wires;
        }




        /// <summary>
        ///     Edgeses the specified distinct.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_Edge&gt;.</returns>
        public static List<TopoDS_Edge> Edges(this TopoDS_Shape shape, bool distinct = true)
        {
            var edges = new List<TopoDS_Edge>();

            var exp = new TopExp_Explorer(shape, TopAbs_ShapeEnum.TopAbs_EDGE, TopAbs_ShapeEnum.TopAbs_SHAPE);
            while (exp.More)
            {
                var edge = exp.Current as TopoDS_Edge;
                exp.Next();

                if (distinct)
                {
                    var otherEdgeIndex = edges.FindIndex(e => e.IsSame(edge));
                    if (otherEdgeIndex >= 0)
                    {
                        if (edges[otherEdgeIndex].Orientation == TopAbs_Orientation.TopAbs_REVERSED
                            && edge.Orientation == TopAbs_Orientation.TopAbs_FORWARD)
                            // Replace with forward edge, this is prefered
                            edges[otherEdgeIndex] = edge;

                        // Edge already present or replaced, skip adding
                        continue;
                    }
                }

                edges.Add(edge);
            }

            return edges;
        }


        /// <summary>
        ///     Verticeses the specified distinct.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <param name="distinct">if set to <c>true</c> [distinct].</param>
        /// <returns>List&lt;TopoDS_Vertex&gt;.</returns>
        public static IEnumerable<TopoDS_Vertex> Vertices(this TopoDS_Shape shape, bool distinct = true)
        {
            var vertexs = shape.Explorer<TopoDS_Vertex>();
            if (distinct)
                vertexs = vertexs.Distinct();
            return vertexs;
        }


        /// <summary>
        ///     Centers the of mass.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>gp_Pnt.</returns>
        public static gp_Pnt CenterOfMass(this TopoDS_Shape shape)
        {
            BRepGProp.SurfaceProperties(shape, out var massProps);
            return massProps.CentreOfMass;
        }


        /// <summary>
        ///     Converts to vertex.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TopoDS_Vertex.</returns>
        public static TopoDS_Vertex ToVertex(this TopoDS_Shape shape)
        {
            return shape == null ? null : shape as TopoDS_Vertex;
        }


        /// <summary>
        ///     Converts to edge.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TopoDS_Edge.</returns>
        public static TopoDS_Edge ToEdge(this TopoDS_Shape shape)
        {
            return shape == null ? null : shape as TopoDS_Edge;
        }


        /// <summary>
        ///     Converts to wire.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TopoDS_Wire.</returns>
        public static TopoDS_Wire ToWire(this TopoDS_Shape shape)
        {
            return shape == null ? null : shape as TopoDS_Wire;
        }


        /// <summary>
        ///     Converts to face.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TopoDS_Face.</returns>
        public static TopoDS_Face ToFace(this TopoDS_Shape shape)
        {
            return shape == null ? null : shape as TopoDS_Face;
        }


        /// <summary>
        ///     Converts to shell.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TopoDS_Shell.</returns>
        public static TopoDS_Shell ToShell(this TopoDS_Shape shape)
        {
            return shape == null ? null : shape as TopoDS_Shell;
        }


        /// <summary>
        ///     Converts to solid.
        /// </summary>
        /// <param name="shape">The shape.</param>
        /// <returns>TopoDS_Solid.</returns>
        public static TopoDS_Solid ToSolid(this TopoDS_Shape shape)
        {
            return shape == null ? null : shape as TopoDS_Solid;
        }


        /// <summary>
        ///     Curves the specified first parameter.
        /// </summary>
        /// <param name="edge">The edge.</param>
        /// <param name="firstParam">The first parameter.</param>
        /// <param name="lastParam">The last parameter.</param>
        /// <returns>Geom_Curve.</returns>
        public static Geom_Curve Curve(this TopoDS_Edge edge, out double firstParam, out double lastParam)
        {
            firstParam = lastParam = 0;
            return edge == null ? null : BRep_Tool.Curve(edge, out firstParam, out lastParam);
        }

        /// <summary>
        ///     Surfaces the specified face.
        /// </summary>
        /// <param name="face">The face.</param>
        /// <returns>Geom_Surface.</returns>
        public static Geom_Surface Surface(this TopoDS_Face face)
        {
            return face == null ? null : BRep_Tool.Surface(face);
        }

        /// <summary>
        ///     Adaptors the specified edge.
        /// </summary>
        /// <param name="edge">The edge.</param>
        /// <returns>BRepAdaptor_Curve.</returns>
        public static BRepAdaptor_Curve Adaptor(this TopoDS_Edge edge)
        {
            return edge == null ? null : new BRepAdaptor_Curve(edge);
        }

        /// <summary>
        ///     Adaptors the specified face.
        /// </summary>
        /// <param name="face">The face.</param>
        /// <returns>BRepAdaptor_Surface.</returns>
        public static BRepAdaptor_Surface Adaptor(this TopoDS_Face face)
        {
            return face == null ? null : new BRepAdaptor_Surface(face);
        }

        /// <summary>
        ///     Determines whether the specified item contains same.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">The list.</param>
        /// <param name="item">The item.</param>
        /// <returns><c>true</c> if the specified item contains same; otherwise, <c>false</c>.</returns>
        public static bool ContainsSame<T>(this IEnumerable<T> list, TopoDS_Shape item) where T : TopoDS_Shape
        {
            if (item == null)
            {
                foreach (var topoDsShape in list)
                    if (topoDsShape == null)
                        return true;
                return false;
            }

            foreach (var topoDsShape in list)
                if (item.IsSame(topoDsShape))
                    return true;
            return false;
        }

        /// <summary>
        ///     Determines whether the specified item contains partner.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">The list.</param>
        /// <param name="item">The item.</param>
        /// <returns><c>true</c> if the specified item contains partner; otherwise, <c>false</c>.</returns>
        public static bool ContainsPartner<T>(this IEnumerable<T> list, TopoDS_Shape item) where T : TopoDS_Shape
        {
            if (item == null)
            {
                foreach (var topoDsShape in list)
                    if (topoDsShape == null)
                        return true;
                return false;
            }

            foreach (var topoDsShape in list)
                if (item.IsPartner(topoDsShape))
                    return true;
            return false;
        }

        /// <summary>
        ///     Indexes the of same.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">The list.</param>
        /// <param name="item">The item.</param>
        /// <returns>System.Int32.</returns>
        public static int IndexOfSame<T>(this IList<T> list, TopoDS_Shape item) where T : TopoDS_Shape
        {
            var size = list.Count;
            if (item == null)
            {
                for (var i = 0; i < size; i++)
                    if (list[i] == null)
                        return i;
                return -1;
            }

            for (var i = 0; i < size; i++)
                if (item.IsSame(list[i]))
                    return i;
            return -1;
        }

        /// <summary>
        /// 计算包围框
        /// </summary>
        /// <param name="shape"></param>
        /// <returns></returns>
        public static Bnd_Box GetBndBox(this TopoDS_Shape shape)
        {
            var box = new Bnd_Box();
            box.Gap = 1.0E-4;
            BRepBndLib.Add(shape, box);
            return box;
        }
    }
}